﻿namespace Refract.Navigation
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Reflection;
    using System.Windows;

    using Refract.Composition;
    using Refract.Extensions;
    using Refract.MVVM;
    using Refract.Services;

    /// <summary>
    /// Implements navigation features for a module controller approach of view discovery as well as 
    /// composition based discovery.
    /// </summary>
    [Export(typeof(INavigationService))]
    public class NavigationService : Service, INavigationService
    {
        #region Properties

        /// <summary>
        /// Gets or sets the module service.
        /// </summary>
        /// <value>The module service.</value>
        [Import]
        public IModuleService ModuleService
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="completedCallback">The completed callback.</param>
        public void GetView(Uri uri, Action<View> completedCallback)
        {
            var actionInfo = new ActionInfo(uri);

            FindController(actionInfo, moduleController =>
                {
                    if (moduleController == null)
                    {
                        View view = FindView(actionInfo);
                        if(completedCallback!= null) completedCallback(view);
                    }
                    else
                    {
                        var moduleType = moduleController.GetType();

                        var methodInfo = moduleType.GetMethod(actionInfo.ActionName);
                        var view = methodInfo == null ? FindView(actionInfo) : InvokeAction(actionInfo, moduleController, methodInfo);

                        if (completedCallback != null) completedCallback(view);
                    }
                });
        }

        /// <summary>
        /// Finds a view within a module.
        /// </summary>
        /// <param name="actionInfo">The parsed action uri.</param>
        /// <returns></returns>
        private static View FindView(ActionInfo actionInfo)
        {
            string qualifiedViewName = ActionInfo.SolutionNamespace + "." + actionInfo.ModuleName + "." + actionInfo.ActionName;
            var view = Container.Current.ExportNewValue(qualifiedViewName) as View;
            return view;
        }

        /// <summary>
        /// Invokes an action using reflection.
        /// </summary>
        /// <param name="actionInfo">The action info.</param>
        /// <param name="moduleController">The module controller.</param>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        private static View InvokeAction(ActionInfo actionInfo, IModuleController moduleController, MethodInfo action)
        {
            var parameterInfoList = action.GetParameters();
            object[] parameters = null;

            if (parameterInfoList.Length > 0)
            {
                parameters = new object[parameterInfoList.Length];

                for (int i = 0; i < parameterInfoList.Length; i++)
                {
                    var parameter = parameterInfoList[i];
                    object value;
                    try
                    {
                        value = Convert.ChangeType(actionInfo.ParameterList[parameter.Name], parameter.ParameterType, null);
                    }
                    catch
                    {
                        value = null;
                    }

                    parameters[i] = value;
                }
            }

            return action.Invoke(moduleController, parameters) as View;
        }

        /// <summary>
        /// Finds a module controller within a loaded module or attempts to load a module that may contain the controller.
        /// </summary>
        /// <param name="actionInfo">The action info.</param>
        /// <param name="completedCallback">The completed callback.</param>
        private void FindController(ActionInfo actionInfo, Action<IModuleController> completedCallback)
        {
            string packageName = ActionInfo.SolutionNamespace + "." + actionInfo.ModuleName + ".xap";
            ModuleService.EnsureModuleIsLoaded(packageName, () =>
            {
                IModuleController moduleController = ModuleService.GetController(actionInfo.ControllerName);
                if (completedCallback != null) completedCallback( moduleController);

            });
        }

        #endregion Methods

        #region Nested Types

        /// <summary>
        /// A class to hold a parsed action uri.
        /// </summary>
        private class ActionInfo
        {
            #region Constructors

            public ActionInfo(Uri uri)
            {
                string path = uri.OriginalString;
                string query = string.Empty;

                if (path.Contains("?"))
                {
                    query = path.Split('?')[1];
                    path = path.Split('?')[0];
                }

                if (path.StartsWith("/")) path = path.Remove(0, 1);
                if (path.EndsWith("/")) path = path.Remove(path.Length - 1);
                string[] splitPath = path.Split('/');

                if (splitPath.Length == 1)
                {
                    ActionName = splitPath[0];
                }
                else if (splitPath.Length == 2)
                {
                    ControllerName = splitPath[0];
                    ActionName = splitPath[1];

                    ModuleName = ControllerName;
                    if (ModuleName.EndsWith("Controller")) ModuleName.Remove(ModuleName.Length - 6);
                    if (!ControllerName.EndsWith("Controller")) ControllerName += "Controller";

                }
                ParameterList = new Dictionary<string, string>();
                if (!string.IsNullOrEmpty(query))
                {
                    string[] splitQuery = query.Split(',');
                    foreach (string queryValue in splitQuery)
                    {
                        if (queryValue.Contains("="))
                        {
                            string[] valueSplit = queryValue.Split('=');
                            ParameterList[valueSplit[0]] = valueSplit[1];
                        }
                    }
                }
            }

            #endregion Constructors

            #region Properties

            public static string SolutionNamespace
            {
                get
                {
                    if (Application.Current == null) return string.Empty;
                    string solutionNamespace = Application.Current.GetType().AssemblyQualifiedName;
                    solutionNamespace = solutionNamespace.Remove(solutionNamespace.IndexOf(".App"));
                    return solutionNamespace;
                }
            }

            public string ActionName
            {
                get; private set;
            }

            public string ControllerName
            {
                get; private set;
            }

            public string ModuleName
            {
                get; private set;
            }

            public Dictionary<String, String> ParameterList
            {
                get; private set;
            }

            #endregion Properties
        }

        #endregion Nested Types
    }
}